import React from 'react';

export default () => {
  // 接口指定结构
  interface LabelledValue {
    label: string;
  }

  function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
  }

  let myObj = {size: 10, label: 'Size 10 Object'};
  printLabel(myObj);

  // 只读属性
  interface Point {
    readonly x: number;
    readonly y: number;
  }

  let p1: Point = {x: 10, y: 20};
  // p1.x = 5; // error!

  // ReadonlyArray
  let a: number[] = [1, 2, 3, 4];
  let ro: ReadonlyArray<number> = a;
  // ro[0] = 12; // error!
  // ro.push(5); // error!
  // ro.length = 100; // error!
  // a = ro; // error!

  // TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过 额外属性检查
  interface SquareConfig {
    color?: string;
    width?: number;
  }

  function createSquare(config: SquareConfig) {
    return config;
  }

  let mySquare = createSquare({colour: 'red', width: 100});
  // 使用类型断言可以绕开这些额外检查
  let mySquare2 = createSquare({colour: 'red', width: 100} as SquareConfig);

  // 最佳的方式是能够添加一个字符串索引签名，表示的是SquareConfig可以有任意数量的属性，并且只要它们不是color和width，那么就无所谓它们的类型是什么
  interface SquareConfig {
    color?: string;
    width?: number;

    [propName: string]: any;
  }

  function createSquare2(config: SquareConfig) {
    return config;
  }

  let mySquare3 = createSquare({colour: 'red', width: 100});
  // 接口也可以描述函数类型，函数的参数名不需要与接口里定义的名字相匹配
  // 要求对应位置上的参数类型是兼容的
  // 如果你不想指定类型，TypeScript的类型系统会推断出参数类型
  interface SearchFunc {
    (source: string, subString: string): boolean;
  }

  let mySearch: SearchFunc = function (src: string, sub: string) {
    let result = src.search(sub);
    return result > -1;
  };
  // 可以描述那些能够“通过索引得到”的类型，比如a[10]或ageMap["daniel"]
  // 可索引类型具有一个 索引签名，它描述了对象索引的类型，还有相应的索引返回值类型
  interface StringArray {
    [index: number]: string;
  }

  let myArray: StringArray;
  myArray = ['Bob', 'Fred'];

  let myStr: string = myArray[0];

  // TypeScript支持两种索引签名：字符串和数字。 可以同时使用两种类型的索引，但是数字索引的返回值必须是字符串索引返回值类型的子类型。
  interface Animal {
    name: string;
  }

  interface Dog extends Animal {
    breed: string;
  }

  interface NotOkay {
    [x: string]: Animal;

    [x: number]: Dog;
  }

  // 使用索引类型 ts 也会确保所有属性与其返回值类型相匹配
  interface NumberDictionary {
    [index: string]: number;

    length: number; // 可以，length是number类型
    // name: string       // 错误，`name`的类型与索引类型返回值的类型不匹配
  }

  // 可以将索引签名设置为只读，这样就防止了给索引赋值
  interface ReadonlyStringArray {
    readonly [index: number]: string;
  }

  let myArray2: ReadonlyStringArray = ['Alice', 'Bob'];
  // myArray2[2] = "Mallory"; // error!

  // 接口继承
  interface Shape {
    color: string;
  }

  interface PenStroke {
    penWidth: number;
  }

  interface Square extends Shape, PenStroke {
    sideLength: number;
  }

  let square = {} as Square;
  square.color = 'blue';
  square.sideLength = 10;
  square.penWidth = 20;

  // 混合类型，有时希望一个对象可以同时具有多种类型
  // 一下定义了函数类型，并且变量有个 reset 函数，有个 interval 属性
  interface Counter {
    (start: number): string;

    interval: number;

    reset(): void;
  }

  function getCounter(): Counter {
    let counter = function (start: number) {
      return start + '';
    } as Counter;
    counter.interval = 123;
    counter.reset = function () {
      counter.interval = 123;
    };
    return counter;
  }

  let c = getCounter();
  c(10);
  c.interval = 5.0;
  console.log(c.interval);
  c.reset();
  console.log(c.interval);

  return (
    <div>
      <h1>接口</h1>
      <p>
        1. 类型检查器不会去检查属性的顺序，只要相应的属性存在并且类型也是对的就可以。只要传入的对象满足上面提到的必要条件，那么它就是被允许的。
      </p>
      <p>2. 可选属性，只是在可选属性名字定义的后面加一个?符号</p>
      <p>
        3. 可选属性的好处之一是可以对可能存在的属性进行预定义，好处之二是可以捕获引用了不存在的属性时的错误
      </p>
      <p>4. 可以在属性名前用 readonly来指定只读属性</p>
      <p>
        5. TypeScript具有 ReadonlyArray 类型，可以确保数组创建后再也不能被修改
      </p>
      <p>6. 就算把整个ReadonlyArray赋值到一个普通数组也是不可以的</p>
      <p>
        7. 使用类型断言可以绕开额外检查。但是最佳的方式是能够添加一个<b>字符串索引签名</b>，可以有任意数量的属性
      </p>
      <p>8. 最好还是定义全接口类型</p>
      <p>
        9. 接口可以描述函数类型，匹配对应参数位置上的类型，不指定类型，ts会自行推断
      </p>
      <p>
        10. 类定义接口类型，可以定义属性，但是constructor属于静态部分，不在检查的范围内
      </p>
      <p>11. 接口可以继承接口</p>
      <p>
        12. 当接口继承了一个类类型时，它会继承类的成员但不包括其实现，并且成员只有子类才可以访问
      </p>
    </div>
  );
};

// 类类型
// 可以在接口中描述一个方法
interface ClockInterfacea {
  currentTime: Date;

  setTime(d: Date): void;
}

class Clock implements ClockInterfacea {
  currentTime!: Date;

  setTime(d: Date) {
    this.currentTime = d;
  }

  constructor(h: number, m: number) {
  }
}

// constructor存在于类的静态部分，所以不在检查的范围内
// 下面定义了实例部分 函数 tick
interface ClockConstructor {
  new(hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

class DigitalClock implements ClockInterface {
  constructor(h: number, m: number) {
  }

  tick() {
    console.log('beep beep');
  }
}

class AnalogClock implements ClockInterface {
  constructor(h: number, m: number) {
  }

  tick() {
    console.log('tick tock');
  }
}

function createClock(
  ctor: ClockConstructor,
  hour: number,
  minute: number,
): ClockInterface {
  return new ctor(hour, minute);
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

// 当接口继承了一个类类型时，它会继承类的成员但不包括其实现
class Control {
  private state: any;
  is_last: any;
}

interface SelectableControl extends Control {
  select(): void;
}

class Button extends Control implements SelectableControl {
  select() {
  }
}

class TextBox extends Control {
  select() {
  }
}

// 错误：“Image”类型缺少“state”属性。
// 并且 state 是私有属性，直接 Control 的子类才可以访问
// is_last 不是私有属性，自己声明即可
// class Image implements SelectableControl {
//   is_last: any;
//
//   select() {
//   }
// }

